package globox_db_server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import utils.jn.Sit;
import utils.jn.TheaterSit;

public class Server extends UnicastRemoteObject implements TheaterManager,
		Serializable {

	public static void main(String[] args) throws RemoteException,
			MalformedURLException {

		// if (System.getSecurityManager() == null) {
		// System.setSecurityManager(new SecurityManager());
		// }

		Server server = args[0].split(":")[0] == "path" ? new Server(
				args[0].split(":")[1]) : new Server();
		int flag = args[0].split(":")[0] == "path" ? 1 : 0;
		Registry r = LocateRegistry.createRegistry(new Integer(args[flag]));
		System.out.println("Registry is on " + args[flag]);
		Naming.rebind(args[flag + 1], server);
		r.rebind(args[flag + 1], server);
		System.out.println("Server " + args[flag + 1] + " is on.");
	}

	private static final int STD_TIMEOUT = 10;

	/**
	 * 
	 */
	private static final long serialVersionUID = 1073922100873951323L;

	private static final Integer NUM_MAX_THEATERS = 1400;

	private static final int NOROW = 26;

	private static final int NOCOLUMN = 40;

	private Map<String, SessionHandler> theaters;
	private Lock mutex;

	private Queue<Integer> transactionsIDs;
	private HashMap<Integer, Long> timeouts;
	private HashMap<Integer, String> tn;
	private DBThread clock;
	private int timeout;

	protected Server() throws RemoteException {
		this.theaters = new HashMap<String, SessionHandler>();
		if (this.theaters.isEmpty())
			generateEntries(this.theaters);
		this.mutex = new ReentrantLock();
		this.timeout = STD_TIMEOUT;
		this.transactionsIDs = new LinkedList<Integer>();
		this.timeouts = new HashMap<Integer, Long>();
		this.tn = new HashMap<Integer, String>();
		this.clock = new DBThread(this);
		this.clock.start();
	}

	private void generateEntries(Map<String, SessionHandler> theaters2) {
		// generate 1400 theaters each with 26 rows and 40 columns
		String characters = "ABCDEFGHIJKLMNOPQRSTUVXYWZabcdefghijklmnopqrstuvxywz";
		Random rng = new Random();
		List<String> tmp = new ArrayList<String>();
		int length = 14;
		int i = 0;
		while (true) {
			String name = generateString(rng, characters, length);
			if (!tmp.contains(name)) {
				try {
					SessionHandler sh = new SessionHandler(name, NOROW,
							NOCOLUMN);
					this.theaters.put(name, sh);
					tmp.add(name);
					System.out.println("Created theater " + name);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			if (i >= NUM_MAX_THEATERS - 1)
				break;
			i++;
		}
	}

	public String generateString(Random rng, String characters, int length) {
		char[] text = new char[length];
		for (int i = 0; i < length; i++) {
			text[i] = characters.charAt(rng.nextInt(characters.length()));
		}
		return new String(text);
	}

	@SuppressWarnings("unchecked")
	public Server(String path) throws RemoteException {
		this.theaters = new HashMap<String, SessionHandler>();
		String[] tmp = null;
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					path));
			tmp = (String[]) in.readObject();

			this.timeout = (Integer) in.readObject();
			this.timeouts = (HashMap<Integer, Long>) in.readObject();

			this.tn = (HashMap<Integer, String>) in.readObject();
			this.transactionsIDs = (LinkedList<Integer>) in.readObject();

			in.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		for (String name : tmp)
			this.theaters.put(name, SessionHandler.load(name + ".theater"));

		this.mutex = new ReentrantLock();
		this.clock = new DBThread(this);
		this.clock.start();
	}

	@Override
	public Iterable<String> getTheaters() throws RemoteException {
		System.out.println(System.currentTimeMillis() + " theaters asked for");
		List<String> all = new ArrayList<String>();
		for (String k : this.theaters.keySet())
			all.add(k);
		return all;
	}

	@Override
	public Iterable<Sit> makeReserve(String session, int transactionID)
			throws RemoteException {
		Iterable<Sit> result = null;
		if (this.theaters.keySet().contains(session)) {
			try {
				this.mutex.lock();
				if (this.transactionsIDs.remove(transactionID))
					this.timeouts.remove(transactionID);

				result = this.theaters.get(session).makeReserve(transactionID);
				this.transactionsIDs.offer(transactionID);
				this.timeouts.put(transactionID, System.currentTimeMillis());
				this.tn.put(transactionID, session);
			} finally {
				this.mutex.unlock();
			}
		}
		System.out.println(System.currentTimeMillis()
				+ " reservation asked for");
		return result;
	}

	@Override
	public Iterable<Sit> makeReserve(String session, int transactionID,
			Iterable<Sit> sits) throws RemoteException {
		Iterable<Sit> result = null;
		if (this.theaters.keySet().contains(session)) {
			try {
				this.mutex.lock();
				if (this.transactionsIDs.remove(transactionID))
					this.timeouts.remove(transactionID);

				result = this.theaters.get(session).makeReserve(transactionID,
						sits);
				this.transactionsIDs.offer(transactionID);
				this.timeouts.put(transactionID, System.currentTimeMillis());
				this.tn.put(transactionID, session);
			} finally {
				this.mutex.unlock();
			}
		}
		System.out.println(System.currentTimeMillis()
				+ " reservation asked for");
		return result;
	}

	@Override
	public boolean commit(int transactionID) throws RemoteException {
		boolean result = false;
		try {
			this.mutex.lock();
			if (this.transactionsIDs.remove(transactionID)) {
				this.timeouts.remove(transactionID);

				this.theaters.get(this.tn.get(transactionID)).commit(
						transactionID);
				this.tn.remove(transactionID);
			}
		} finally {
			this.mutex.unlock();
		}
		System.out.println(System.currentTimeMillis()
				+ " reservation finished with "
				+ (result ? "sucess" : "insucess"));
		return result;
	}

	@Override
	public void addTheater(String name, int noRow, int noColumn)
			throws RemoteException {
		try {
			this.mutex.lock();
			this.theaters.put(name, new SessionHandler(name, noRow, noColumn));
		} finally {
			this.mutex.unlock();
		}
		System.out.println(System.currentTimeMillis() + " add theater " + name
				+ " " + noRow + " " + noColumn);
	}

	@Override
	public boolean removeTheater(String name) throws RemoteException {
		return false;
	}

	@Override
	public void changeTimeOut(int nto) throws RemoteException {
		this.timeout = nto;
		System.out.println(System.currentTimeMillis() + " timout changed to "
				+ nto);
	}

	@Override
	public void save(String path) throws RemoteException {
		try {
			for (String shname : this.theaters.keySet()) {
				this.theaters.get(shname).save(shname + ".theater");
				System.out.println(System.currentTimeMillis() + " theater "
						+ shname + " saved");
			}
			File data = new File(path);
			ObjectOutputStream out = new ObjectOutputStream(
					new FileOutputStream(data));
			out.writeObject(this.theaters.keySet().toArray());
			out.writeObject(this.timeout);
			out.writeObject(this.timeouts);
			out.writeObject(this.tn);
			out.writeObject(this.transactionsIDs);
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(System.currentTimeMillis() + " server saved");
	}

	class DBThread extends Thread implements Serializable {

		/**
		 * 
		 */
		private static final long serialVersionUID = -3307322125591346172L;
		private Server server;
		private Object o;
		private long tm;

		public DBThread(Server o) {
			super();
			this.server = o;
			this.tm = o.timeout * 1000;
			this.o = new Object();
		}

		public void run() {

			while (true) {
				int shid = 0;
				synchronized (this.o) {
					try {
						this.o.wait(this.tm);
					} catch (InterruptedException e) {
						e.printStackTrace();
						System.exit(MAX_PRIORITY);
					}
					try {
						this.server.mutex.lock();
						while (this.server.transactionsIDs.peek() != null
								&& System.currentTimeMillis()
										- this.server.timeouts
												.get(this.server.transactionsIDs
														.peek()) >= this.server.timeout) {
							shid = this.server.transactionsIDs.poll();
							this.server.theaters.get(this.server.tn.get(shid))
									.free(shid);
							this.server.timeouts.remove(shid);
							this.server.tn.remove(shid);
						}
						this.tm = this.server.timeouts.size() > 0 ? this.server.timeouts
								.get(this.server.transactionsIDs.peek())
								+ this.server.timeout
								* 1000
								- System.currentTimeMillis()
								: this.server.timeout;

					} finally {
						this.server.mutex.unlock();
					}
				}
			}
		}

	}

	@Override
	public Iterable<Sit> getSeatsByTheater(String t)
			throws RemoteException {
		//
		SessionHandler sh = this.theaters.get(t);
		if (sh != null)
			return sh.getSeats();
		else
			return null;
	}
}
